package com.zlst.uimp.cas.authentication;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.MessageDescriptor;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.exceptions.AccountDisabledException;
import org.apereo.cas.authentication.exceptions.AccountPasswordMustChangeException;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.support.password.PasswordExpiringWarningMessageDescriptor;
import org.apereo.cas.services.ServicesManager;
import org.springframework.jdbc.core.JdbcTemplate;

import org.apereo.cas.util.CollectionUtils;

import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.auth.login.FailedLoginException;
import javax.sql.DataSource;

/**
 * 自定义认证处理器
 * Created by 170121 on 2018/6/8.
 */
public class UimpAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(UimpAuthenticationHandler.class);

    /**
     * The Algorithm name.
     */
    protected String algorithmName;

    /**
     * The Sql statement to execute.
     */
    protected String sql;

    /**
     * The Password field name.
     */
    protected String passwordFieldName = "password";

    /**
     * The Salt field name.
     */
    protected String saltFieldName = "salt";

    /**
     * The Expired field name.
     */
    protected String expiredFieldName;

    /**
     * The Expired field name.
     */
    protected String disabledFieldName;

    /**
     * The Number of iterations field name.
     */
    protected String numberOfIterationsFieldName;

    /**
     * The number of iterations. Defaults to 0.
     */
    protected long numberOfIterations;

    /**
     * Static/private salt to be combined with the dynamic salt retrieved
     * from the database. Optional.
     * <p>If using this implementation as part of a password hashing strategy,
     * it might be desirable to configure a private salt.
     * A hash and the salt used to compute it are often stored together.
     * If an attacker is ever able to access the hash (e.g. during password cracking)
     * and it has the full salt value, the attacker has all of the input necessary
     * to try to brute-force crack the hash (source + complete salt).</p>
     * <p>However, if part of the salt is not available to the attacker (because it is not stored with the hash),
     * it is much harder to crack the hash value since the attacker does not have the complete inputs necessary.
     * The privateSalt property exists to satisfy this private-and-not-shared part of the salt.</p>
     * <p>If you configure this attribute, you can obtain this additional very important safety feature.</p>
     */
    protected String staticSalt;

    private final JdbcTemplate jdbcTemplate;

    private final DataSource dataSource;

    private final Map<String, Collection<String>> principalAttributeMap;

    public UimpAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
                                     final Integer order, final DataSource dataSource,
                                     final String algorithmName, final String sql, final String passwordFieldName,
                                     final String saltFieldName, final String expiredFieldName, final String disabledFieldName,
                                     final String numberOfIterationsFieldName, final long numberOfIterations,
                                     final String staticSalt,final Map<String, Collection<String>> attributes) {
        super(name, servicesManager, principalFactory, order);
        this.dataSource = dataSource;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.algorithmName = algorithmName;
        this.sql = sql;
        this.passwordFieldName = passwordFieldName;
        this.saltFieldName = saltFieldName;
        this.expiredFieldName = expiredFieldName;
        this.disabledFieldName = disabledFieldName;
        this.numberOfIterationsFieldName = numberOfIterationsFieldName;
        this.numberOfIterations = numberOfIterations;
        this.staticSalt = staticSalt;
        this.principalAttributeMap = attributes;
    }

    @Override
    protected HandlerResult authenticateUsernamePasswordInternal(UsernamePasswordCredential transformedCredential, String originalPassword) throws GeneralSecurityException, PreventedException {
        String username = transformedCredential.getUsername();
        String encodedPassword = transformedCredential.getPassword();
        LOGGER.debug("==================自定义身份验证===========================" + username);
        LOGGER.error("==================自定义身份验证：" + this.toString());

        //用户属性
        final Map<String, Object> attributes = new LinkedHashMap<>(this.principalAttributeMap.size());

        final Map<String, Object> values = jdbcTemplate.queryForMap(this.sql, username);
        final String digestedPassword = digestEncodedPassword(encodedPassword, values);
        final String dbPassword = (String) values.get(this.passwordFieldName);

        if (StringUtils.isNotBlank(digestedPassword) && !matches(digestedPassword, dbPassword)) {
            throw new FailedLoginException("Password does not match value on record.");
        }
        if (StringUtils.isNotBlank(this.disabledFieldName)) {
            final Object dbDisabled = values.get(this.disabledFieldName);
            if (dbDisabled != null && (Boolean.TRUE.equals(BooleanUtils.toBoolean(dbDisabled.toString())) || dbDisabled.equals(Integer.valueOf(1)))) {
                throw new AccountDisabledException("Account has been disabled");
            }
        }
        if (StringUtils.isNotBlank(this.expiredFieldName)) {
            final Object dbExpired = values.get(this.expiredFieldName);
            if (dbExpired != null && (Boolean.TRUE.equals(BooleanUtils.toBoolean(dbExpired.toString())) || dbExpired.equals(1))) {
                throw new AccountPasswordMustChangeException("Password has expired");
            }
        }
        this.principalAttributeMap.forEach((key, attributeNames) -> {
            final Object attribute = values.get(key);

            if (attribute != null) {
                LOGGER.debug("Found attribute [{}] from the query results", key);
                attributeNames.forEach(s -> {
                    LOGGER.debug("Principal attribute [{}] is virtually remapped/renamed to [{}]", key, s);
                    attributes.put(s, CollectionUtils.wrap(attribute.toString()));
                });
            } else {
                LOGGER.warn("Requested attribute [{}] could not be found in the query results", key);
            }

        });




        List<MessageDescriptor> warnings = new ArrayList<>();
        PasswordExpiringWarningMessageDescriptor warnning = new PasswordExpiringWarningMessageDescriptor("密码即将过期，请修改密码！", 3);

        return createHandlerResult(transformedCredential, this.principalFactory.createPrincipal(username, attributes), null);
    }


    private String digestEncodedPassword(final String encodedPassword, final Map<String, Object> values) {
//        final ConfigurableHashService hashService = new DefaultHashService();
//
//        if (StringUtils.isNotBlank(this.staticSalt)) {
//            hashService.setPrivateSalt(ByteSource.Util.bytes(this.staticSalt));
//        }
//        hashService.setHashAlgorithmName(this.algorithmName);
//
//        Long numOfIterations = this.numberOfIterations;
//        if (values.containsKey(this.numberOfIterationsFieldName)) {
//            final String longAsStr = values.get(this.numberOfIterationsFieldName).toString();
//            numOfIterations = Long.valueOf(longAsStr);
//        }
//
//        hashService.setHashIterations(numOfIterations.intValue());
//        if (!values.containsKey(this.saltFieldName)) {
//            throw new IllegalArgumentException("Specified field name for salt does not exist in the results");
//        }
//
//        final String dynaSalt = values.get(this.saltFieldName).toString();
//        final HashRequest request = new HashRequest.Builder()
//            .setSalt(dynaSalt)
//            .setSource(encodedPassword)
//            .build();
//        return hashService.computeHash(request).toHex();
        return "123456";
    }

    @Override
    public String toString() {
        return "UimpAuthenticationHandler{" +
            "algorithmName='" + algorithmName + '\'' +
            ", sql='" + sql + '\'' +
            ", passwordFieldName='" + passwordFieldName + '\'' +
            ", saltFieldName='" + saltFieldName + '\'' +
            ", expiredFieldName='" + expiredFieldName + '\'' +
            ", disabledFieldName='" + disabledFieldName + '\'' +
            ", numberOfIterationsFieldName='" + numberOfIterationsFieldName + '\'' +
            ", numberOfIterations=" + numberOfIterations +
            ", staticSalt='" + staticSalt + '\'' +
            ", jdbcTemplate=" + jdbcTemplate +
            ", dataSource=" + dataSource +
            '}';
    }
}
